package com.haredot.aspectj;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.github.hiwepy.qqwry.spring.boot.ext.QQWry;
import com.haredot.annotation.LogIgnore;
import com.haredot.annotation.LogModule;
import com.haredot.constraint.RabbitConst;
import com.haredot.entity.Logger;
import com.haredot.holder.LogContextHolder;
import com.haredot.properties.LogProperties;
import com.haredot.utils.ClientIPUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * @author haredot
 * @since 2023/08/26
 *  默认该类记录 com..controller 包下的所有类 操作日志
 *  可以通过 sys.log 相关配置 进行 日志控制
 *  如果 controller 包下的某个类 或者 某个方法 不需要记录日志，只需要在 类/方法上添加 @LogIgnore 注解即可
 */
@Aspect
public class LogAspect {

    private org.slf4j.Logger logger = LoggerFactory.getLogger(LogAspect.class);

    @Pointcut("execution(* com..controller.*.*(..))")
    public void pointCut() {
    }

    @Value("${spring.application.name}")
    private String appName;

    private LogProperties logProperties;

    @Resource
    private HttpServletRequest request;
    @Resource
    private QQWry qqwry;

    private RabbitTemplate rabbitTemplate;

    public LogAspect(LogProperties logProperties, RabbitTemplate rabbitTemplate) {
        this.logProperties = logProperties;
        this.rabbitTemplate = rabbitTemplate;
    }

    @Before("pointCut()")
    public void before(JoinPoint joinPoint) {

        logger.debug("进入了 logAspect 切面类、准备进行日志记录");
        // 判断是否启用记录业务日志、且该请求方式是否需要记录日志,  判断目标类、 或者目标方法上是否有 LogIgnore 注解， 如果有，则不记录日志
        if (!logProperties.getServiceLog().isEnable() ||
                !logProperties.getServiceLog().getMethods().contains(HttpMethod.valueOf(request.getMethod())) ||
                joinPoint.getTarget().getClass().isAnnotationPresent(LogIgnore.class) ||
                ((MethodSignature) joinPoint.getSignature()).getMethod().isAnnotationPresent(LogIgnore.class)) {

            logger.debug("请求 {} 不满足记录日志的条件、可能是因为没有开启日志记录、请求方式{}不在日志记录范围内, 类或方法上标记有 LogIgnore注解",
                    request.getServletPath(), request.getMethod());
            LogContextHolder.getLog().setStore(false);
            return;
        }
        LogContextHolder.getLog().setCreateTime(LocalDateTime.now());
        LogContextHolder.getLog().setStore(true);
        LogContextHolder.getLog().setMethod(request.getMethod());
        LogContextHolder.getLog().setAppName(this.appName);

        Class<?> aClass = joinPoint.getTarget().getClass();

        String moduleName = aClass.getSimpleName();
        if (aClass.isAnnotationPresent(LogModule.class)) {
            moduleName = aClass.getAnnotation(LogModule.class).value();
        }
        LogContextHolder.getLog().setModuleName(moduleName);
        LogContextHolder.getLog().setUrl(request.getServletPath());
        // 获取操作人员
        String user = SecurityContextHolder.getContext().getAuthentication().getName();
        LogContextHolder.getLog().setUser(user);
        // 获取 IP 地址
        String clientIP = ClientIPUtil.getClientIP(request);
        LogContextHolder.getLog().setIp(clientIP);
        // 获取 IP 地址对应的地址
        String address = qqwry.findIP(clientIP).getMainInfo();
        LogContextHolder.getLog().setAddress(address);
        // 获取方法名
        String methodName = aClass.getName() + "." + joinPoint.getSignature().getName() + "()";
        LogContextHolder.getLog().setMethodName(methodName);

        List<Object> argList = new ArrayList<>();
        // 获取 参数
        Object[] args = joinPoint.getArgs();

        // 获取要排除的类
        List<Class<?>> excludeRecordArgList = logProperties.getServiceLog().getExcludeRecordArgList();

        outer:
        for (Object arg : args) {
            for (Class<?> clazz : excludeRecordArgList) {
                // 如果 clazz 是 arg 或者 父类型
                if (clazz.isAssignableFrom(arg.getClass())) continue outer;
            }
            argList.add(arg);
        }
        // 获取 序列化后的参数
        String argsJsonString = JSONArray.toJSONString(argList);
        LogContextHolder.getLog().setArgs(argsJsonString);

    }

    @AfterReturning(value = "pointCut()", returning = "ret")
    public void afterReturning(Object ret) {
        if (LogContextHolder.getLog().isStore()) {
            LogContextHolder.getLog().setState(true);
            if (logProperties.getServiceLog().isResult()) {
                LogContextHolder.getLog().setResultText(JSONObject.toJSONString(ret));
            }
        }
    }

    @AfterThrowing(value = "pointCut()", throwing = "e")
    public void afterThrowing(Throwable e) {
        if (LogContextHolder.getLog().isStore()) {
            LogContextHolder.getLog().setState(false);
            LogContextHolder.getLog().setErrorText(e.getMessage());
        }
    }


    @After("pointCut()")
    public void after() {
        if (LogContextHolder.getLog().isStore()) {
            Logger logger = LogContextHolder.getLog();
            String jsonString = JSONArray.toJSONString(logger);
            // 发送到 rabbitmq 队列中
            this.logger.debug("日志信息内容为：{}", jsonString);
            // 发送消息到队列中
            if (rabbitTemplate != null && (logProperties.getServiceLog().getLogType() == LogProperties.LogType.ALL ||
                    (logProperties.getServiceLog().getLogType() == LogProperties.LogType.ERROR && StringUtils.hasLength(logger.getErrorText()) ))) {
                rabbitTemplate.send(RabbitConst.LOG_QUEUE, new Message(jsonString.getBytes(StandardCharsets.UTF_8)));
                this.logger.debug("消息已投递到rabbitmq队列中...");
            }
        }
    }
}
